﻿using System;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace CGF
{
    public class CTaskUpdate : MonoBehaviour
    {
        public event Action onUpdate;
        private void Update()
        {
            onUpdate?.Invoke();
        }
    }

    public class CompleteTask : INotifyCompletion
    {
        public CompleteTask GetAwaiter() => this;
        public void OnCompleted(Action continuation) { }
        public bool IsCompleted => true;
        public object GetResult() { return null; }
    }

    public class CompleteTask<T> : INotifyCompletion
    {
        public CompleteTask<T> GetAwaiter() => this;
        public void OnCompleted(Action continuation) { }
        public bool IsCompleted => true;
        public T GetResult() { return default(T); }
    }

    [AsyncMethodBuilder(typeof(CTaskMethodBuilder))]
    public class CTask : ICriticalNotifyCompletion
    {
        private static CTaskUpdate updateInstance;
        private bool isComplated;
        private Action callback;
        private float timer;
        private bool isForWait;
        private Type resultType;
        private object result;
        private bool hasRegisterUpdate;

        private CTask() { }

        public static CTask Create(Type resultType = null)
        {
            if (!updateInstance)
            {
                updateInstance = GameObject.FindObjectOfType<CTaskUpdate>();
                if (!updateInstance)
                {
                    GameObject go = new GameObject("CTaskUpdate");
                    GameObject.DontDestroyOnLoad(go);
                    updateInstance = go.AddComponent<CTaskUpdate>();
                }
            }
            CTask task = new CTask();
            task.resultType = resultType;
            return task;
        }

        public static CompleteTask Completed { get { return new CompleteTask(); } }

        public CTask GetAwaiter() => this;

        public bool IsCompleted => isComplated;

        public object GetResult() { return result; }

        public object Result { get { return result; } }

        public Type GetResultType() { return resultType; }

        public void Invoke() { }

        public void UnsafeOnCompleted(Action continuation)
        {
            this.callback = continuation;
        }

        public void OnCompleted(Action continuation)
        {
            this.callback = continuation;
        }

        public void Update()
        {
            if (timer > 0 && timer < Time.time)
            {
                timer = 0;
                SetResult();
            }
        }

        public void SetResult(object obj = null)
        {
            isComplated = true;
            result = obj;
            callback?.Invoke();
            if (!isForWait)
            {
                Dispose();
            }
        }

        public void Dispose()
        {
            timer = 0;
            updateInstance.onUpdate -= Update;
            hasRegisterUpdate = false;
            callback = null;
        }

        public static CTask Wait(float seconds)
        {
            CTask task = Create();
            task.timer = Time.time + seconds;
            task.isForWait = true;
            if (!task.hasRegisterUpdate)
            {
                updateInstance.onUpdate += task.Update;
                task.hasRegisterUpdate = true;
            }
            return task;
        }
    }

    [AsyncMethodBuilder(typeof(CTaskMethodBuilder<>))]
    public class CTask<T> : ICriticalNotifyCompletion
    {
        private bool isComplated;
        private Action callback;
        private T result;

        private CTask() { }

        public static CTask<T> Create()
        {
            CTask<T> task = new CTask<T>();
            task.isComplated = false;
            return task;
        }

        public CTask<T> GetAwaiter() => this;

        public bool IsCompleted => isComplated;

        public void Invoke() { }

        public T GetResult() { return result; }

        public T Result { get { return result; } }

        public void UnsafeOnCompleted(Action continuation)
        {
            this.callback = continuation;
        }

        public void OnCompleted(Action continuation)
        {
            this.callback = continuation;
        }

        public void SetResult(T t)
        {
            isComplated = true;
            this.result = t;
            callback?.Invoke();
            callback = null;
        }

        public Type GetResultType()
        {
            return result.GetType();
        }
    }
}
